#pragma once

#include <ossia/detail/pod_vector.hpp>

#include <boost/container/vector.hpp>

#include <halp/controls.hpp>
#include <halp/geometry.hpp>
#include <halp/meta.hpp>
#include <halp/texture.hpp>

#include <algorithm>

#if (!defined(__linux__))
#define SCORE_LIBC_HAS_FLOAT16 1
#elif (defined(__linux__) && defined(__GLIBC__))
#if __GLIBC_PREREQ(2, 40)
#define SCORE_LIBC_HAS_FLOAT16 1
#endif
#endif

// when you enter the least obvious syntax competition and your opponent is clang builtins
#if defined(__is_identifier)
#if (!__is_identifier(_Float16))
#define SCORE_COMPILER_HAS_FLOAT16 1
#endif
#endif

namespace Threedim
{
struct custom_texture
{
  using uninitialized_bytes = boost::container::vector<unsigned char>;
  unsigned char* bytes;
  int width;
  int height;
  enum texture_format : uint8_t
  {
    RGBA8,
    BGRA8,
    R8,
    RG8,
    R16,
    RG16,
    RED_OR_ALPHA8,

    RGBA16F,
    RGBA32F,
    R16F,
    R32F,

    R8UI,
    R32UI,
    RG32UI,
    RGBA32UI,
  } format
      = RGBA8;
  bool changed;

  static constexpr int component_size(texture_format format) noexcept
  {
    switch(format)
    {
      case RGBA8:
      case BGRA8:
        return 1;
      case R8:
        return 1;
      case RG8:
        return 1;
      case R16:
        return 2;
      case RG16:
        return 2;
      case RED_OR_ALPHA8:
        return 1;
      case RGBA16F:
        return 2;
      case RGBA32F:
        return 4;
      case R16F:
        return 2;
      case R32F:
        return 4;
      case R8UI:
        return 1;
      case R32UI:
        return 4;
      case RG32UI:
        return 4;
      case RGBA32UI:
        return 4;
      default:
        return 1;
    }
  }
  static constexpr int components(texture_format format) noexcept
  {
    switch(format)
    {
      case RGBA8:
      case BGRA8:
        return 4;
      case R8:
        return 1;
      case RG8:
        return 2;
      case R16:
        return 1;
      case RG16:
        return 2;
      case RED_OR_ALPHA8:
        return 1;
      case RGBA16F:
        return 4;
      case RGBA32F:
        return 4;
      case R16F:
        return 1;
      case R32F:
        return 1;
      case R8UI:
        return 1;
      case R32UI:
        return 1;
      case RG32UI:
        return 2;
      case RGBA32UI:
        return 4;
      default:
        return 1;
    }
  }

  int bytes_per_pixel() const noexcept
  {
    return component_size(format) * components(format);
  }
  auto bytesize() const noexcept { return bytes_per_pixel() * width * height; }
  auto component_size() const noexcept { return bytes_per_pixel() * width * height; }
  /* FIXME the allocation should not be managed by the plug-in */
  auto allocate(int width, int height)
  {
    using namespace boost::container;
    return uninitialized_bytes(bytesize(), default_init);
  }

  void update(unsigned char* data, int w, int h) noexcept
  {
    bytes = data;
    width = w;
    height = h;
    changed = true;
  }
};
class ArrayToTexture
{
public:
  halp_meta(name, "Array to texture")
  halp_meta(category, "Visuals/Textures")
  halp_meta(c_name, "array_to_texture")
  halp_meta(manual_url, "https://ossia.io/score-docs/processes/array-to-texture.html")
  halp_meta(uuid, "bb5dc513-3430-4671-8c74-2bba78e53709")

  struct ins
  {
    struct : halp::val_port<"Input", std::vector<float>>
    {
      void update(ArrayToTexture& self) { self.recreate(); }
    } in;
    struct : halp::xy_spinboxes_i32<"Size">
    {
      void update(ArrayToTexture& self) { self.recreate(); }
    } size;
    struct : halp::enum_t<custom_texture::texture_format, "Format">
    {
      void update(ArrayToTexture& self) { self.recreate(); }
    } format;
  } inputs;

  struct
  {
    halp::texture_output<"Output", custom_texture> main;
  } outputs;

  void recreate()
  {
    const auto format = inputs.format;
    const auto sz = inputs.size.value;
    outputs.main.texture.format = format;
    outputs.main.create(sz.x, sz.y);
    std::size_t to_copy = sz.x * sz.y * outputs.main.texture.components(format);
    const auto& value = inputs.in.value;
    to_copy = std::min(to_copy, value.size());

    auto* out = outputs.main.texture.bytes;
    using enum custom_texture::texture_format;
    switch(format)
    {
      case RGBA8:
      case BGRA8:
      case R8:
      case RG8:
      case RED_OR_ALPHA8:
      case R8UI:
        std::copy_n(value.data(), to_copy, (uint8_t*)out);
        break;

      case R16:
      case RG16:
        std::copy_n(value.data(), to_copy, (uint16_t*)out);
        break;

      case R32UI:
      case RG32UI:
      case RGBA32UI:
        std::copy_n(value.data(), to_copy, (uint32_t*)out);
        break;

      case R32F:
      case RGBA32F:
        std::copy_n(value.data(), to_copy, (float*)out);
        break;

      case R16F:
      case RGBA16F:

#if (SCORE_LIBC_HAS_FLOAT16 && SCORE_COMPILER_HAS_FLOAT16)
        std::copy_n(value.data(), to_copy, (_Float16*)out);
#endif
        break;

      default:
        break;
    }
    outputs.main.upload();
  }
};

}
